package cn.buddha.jec.mvc.advice;

import cn.buddha.jec.annotation.TongSecurityParameter;
import cn.buddha.jec.exception.enums.TongExceptionEnum;
import cn.buddha.jec.model.TongBaseRequest;
import cn.buddha.jec.model.TongContext;
import cn.buddha.jec.util.TongBeanUtils;
import cn.buddha.jec.util.TongBusinessUtils;
import cn.buddha.jec.util.TongDateUtils;
import cn.buddha.jec.util.TongIOUtils;
import cn.buddha.jec.util.TongValidatorUtils;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdviceAdapter;

import javax.validation.Validator;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;

/**
 * @ClassName TongRequestBodyAdvice
 * @Author 友野浩二
 * @Date 2020/4/12 17:12
 * @Description
 * @Version 1.0
 *
 * <pre>
 * RequestBodyAdvice：针对所有以@RequestBody的参数，在读取请求body之前或者在body转换成对象之前可以做相应的增强。
 *                    需要在controller层中调用方法上加入@RequestMapping和@RequestBody;后者若没有，则RequestBodyAdvice实现方法不执行。
 *     当前类需要 AOP 注解 @ControllerAdvice，其实就是对controller的一个切面
 * RequestBodyAdviceAdapter: RequestBodyAdvice的适配器。
 * 功能:
 *      1- 对请求报文解密；
 *      2- 打印请求报文；
 *      3- 对请求模型的非空字段进行判断；
 *      5- 初始化请求上下文。
 *
 * 【注意】：这个切面除了把业务controller给切了，还有可能把人家三方jar包或者其他框架的controller给切了，所以，有些地方需要做的适配。
 * </pre>
 */
@ControllerAdvice
@Slf4j
public class TongRequestBodyAdvice extends RequestBodyAdviceAdapter {

    /**
     * @return boolean
     * @Author 友野浩二
     * @Description // 如果返回false，则不执行当前 advice 的业务
     * @Date 2020/4/13 10:34
     * @Param [methodParameter, targetType, converterType]
     */
    @Override
    public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        // 判断 controller的方法的[入参]是否有 @RequestBody 注解，如果没有，则返回false，当前 advice 就不会继续执行了。
//        return methodParameter.hasParameterAnnotation(RequestBody.class);
        return true;// 拦截所有controller
    }

    /**
     * @return org.springframework.http.HttpInputMessage
     * @Author 友野浩二
     * @Description // 读取参数前执行(在此可以做 编码/解密/封装参数为对象的操作)
     * @Date 2020/4/13 10:35
     * @Param [inputMessage, parameter, targetType, converterType]
     */
    @Override
    public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
        // 请求数据解密：判断 controller 的方法上是否有 "@TongSecurityParameter 加解密注解"
        boolean hasTsp = parameter.hasMethodAnnotation(TongSecurityParameter.class);
        if (hasTsp) {
            // 获取 "加解密注解"
            TongSecurityParameter tongSecurityParameter = parameter.getMethodAnnotation(TongSecurityParameter.class);
            // 判断是否需要解密
            if (tongSecurityParameter != null && tongSecurityParameter.inDecode()) {
                log.info("开始对方法: {} 的数据进行解密操作", parameter.getMethod());
                String httpInputBodyStr = TongIOUtils.getText(inputMessage.getBody(), StandardCharsets.UTF_8.name());
                TongAdviceLog.logHttpBodyData(parameter, httpInputBodyStr,
                        "TongBaseRequest Start, URI: [{}]{}Clazz: [{}], MethodName: [{}], TongBaseRequest Before decrypt content is: {}");
                return new TongHttpInputMessage(inputMessage, httpInputBodyStr);
            }
        }

        return super.beforeBodyRead(inputMessage, parameter, targetType, converterType);
    }

    /**
     * @return java.lang.Object
     * @Author 友野浩二
     * @Description // 读取参数后执行(这里可以打印请求数据)
     * @Date 2020/4/13 17:03
     * @Param [body 请求对象request中的请求体, inputMessage, parameter, targetType, converterType]
     */
    @Override
    public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        // 1- 打印请求数据
        String URI = TongAdviceLog.logHttpBodyData(parameter,
                JSON.toJSONString(body, SerializerFeature.PrettyFormat, SerializerFeature.WriteDateUseDateFormat),
                "TongBaseRequest Start, URI: [{}]{}Clazz: [{}], MethodName: [{}], TongBaseRequest content is: {}");

        // controller的方法入参有RequestBody注解
        boolean hasRequestBody = parameter.hasParameterAnnotation(RequestBody.class);
        // controller的方法上有ResponseBody注解
        boolean hasResponseBody = parameter.hasMethodAnnotation(ResponseBody.class);
        // controller的类上有RestController(相当于@Controller + @ResponseBody)注解
        RestController restController = parameter.getDeclaringClass().getAnnotation(RestController.class);

        /// 2- 特殊处理
        // 条件一：controller的方法入参有@RequestBody注解；
        // 条件二：controller的方法上有@ResponseBody注解 或者 类上有@RestController注解；
        // 条件三：判断请求模型是否为 TongBaseRequest 的子类(开发规范要求所有的业务请求DTO需要继承TongBaseRequest)
        if (hasRequestBody && (restController != null || hasResponseBody) && (body instanceof TongBaseRequest)) {
            TongBaseRequest tongBaseRequest = (TongBaseRequest) body;// 强转
            // 2-1 接口请求模型基本校验(非空、枚举等)
            Validator validator = TongValidatorUtils.getValidator();
            // 对请求头信息进行校验
            TongValidatorUtils.validateRequest(tongBaseRequest.getTongInHead(), validator);
            // 对接口上送模型进行校验
            TongValidatorUtils.validateRequest(tongBaseRequest, validator);

            // 2-2 请求上下文初始化
            TongContext tongContext = TongContext.builder()
                    .tongInHead(tongBaseRequest.getTongInHead())// 业务请求头信息
                    .tongPageHead(tongBaseRequest.getTongPageHead())// 业务请求头分页信息
                    .reqUrl(URI)
                    .reqClassName(parameter.getDeclaringClass().getName())// URI对应的类名
                    .tranDate(TongDateUtils.getDate())
                    .isRestTongApi(Boolean.TRUE)// 是否满足当前框架对controller的要求,即上面的if条件
                    .build();
            tongContext.init(tongContext);
            log.info("TongContext init: {}", System.lineSeparator() + tongContext);
        }

        return super.afterBodyRead(body, inputMessage, parameter, targetType, converterType);
    }

    /**
     * 实现 HttpInputMessage,对请求httpBody体做一些处理(比如解密)
     */
    class TongHttpInputMessage implements HttpInputMessage {
        private HttpHeaders httpHeaders;
        private InputStream body;

        public TongHttpInputMessage(HttpInputMessage httpInputMessage, String httpInputBodyStr) throws IOException {
            this.httpHeaders = httpInputMessage.getHeaders();
            this.body = decryptBody(httpInputBodyStr);
        }

        @Override
        public InputStream getBody() throws IOException {
            return body;
        }

        @Override
        public HttpHeaders getHeaders() {
            return httpHeaders;
        }

        /**
         * @return java.io.InputStream
         * @Author 友野浩二
         * @Description // 解密
         * @Date 2020/4/13 22:13
         * @Param [httpInputBody]
         */
        private InputStream decryptBody(String httpInputBodyStr) {
            if (TongBeanUtils.isNullObj(httpInputBodyStr)) {
                throw TongBusinessUtils.createBusinessException(TongExceptionEnum.IO_NOT_PARAMETER.getCode(),
                        TongExceptionEnum.IO_NOT_PARAMETER.getMessage());// 无任何参数
            }
            // 此处只进行非常非常简单的解密
            httpInputBodyStr = httpInputBodyStr.replaceAll("-hehe", "");
            return TongIOUtils.toInputStream(httpInputBodyStr, StandardCharsets.UTF_8.name());
        }
    }
}
